/*
 * Copyright 2009 Armando Blancas
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package taskgraph.tasks.data;

import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

import taskgraph.ports.InputPort;
import taskgraph.tasks.PrimitiveTask;


/**
 * Writes an array E[] from elements read through the input port.
 * 
 * <p>The array is available after the graph's execution through the
 * accessor {@code E[] getArray()}. Clients may pass an allocated
 * array to avoid a new allocation, but should take care in calling
 * {@code getArray()} for the correct array reference, since there
 * is no guarantee that the passed array will have enough capacity
 * (in which case a new one is allocated). If the array has enough
 * capacity, that same reference is returned from {@code getArray()}.
 * 
 * <p>Elements written to the array may optionally be copied
 * (or cloned) in order to ensure the safety of concurrent access.
 * If this is requested, elements must provide a copy constructor
 * or clone() method.
 * 
 * @author Armando Blancas
 * @see taskgraph.ports.InputPort
 * @param <E> The type of elements transported by this task.
 */
public class WriteArray<E> extends PrimitiveTask {

	private InputPort<E> input;
	private E[] array;
	private boolean duplicating; 


	/**
	 * This constructor allows creating instances as beans.
	 */
    public WriteArray() {
    	// do nothing
    }

	/**
	 * Constructor. Will allocate a new array.
     * 
     * @param input The element input port.
     */
    public WriteArray(final InputPort<E> input) {
    	setInput(input);
    }

	/**
	 * Constructor.
	 * 
	 * Will try to write elements into the passed {@code array}.
	 * Will allocate a new one if this array's capacity is not
	 * big enough to hold all the elements.
     * 
     * @param input The element input port.
     * @param array An array with the source data.
     */
    public WriteArray(final InputPort<E> input, final E[] array) {
    	setInput(input);
    	setArray(array);
    }

    /**
	 * Gets the array that provides the source data.
	 * 
	 * <p>This method should be used to access the result
	 * of this task. If an array of enough capacity was passed,
	 * then the same reference will be returned here. 
	 * 
	 * @return The reference to the element array.
	 */
	public E[] getArray() {
		return array;
	}

	/**
     * Gets the input port.
     * 
	 * @return The input port.
	 */
	public InputPort<E> getOutput() {
		return input;
	}

	/**
	 * Tests whether the task is making element duplicates.
	 * 
	 * @return The {@code boolean} value.
	 */
	public boolean isDuplicating() {
		return duplicating;
	}

	/**
     * Performs the work of this task.
     * 
	 * Writes elements from the array and writes them to the input port. 
	 *<pre>
	 *If task is duplicating elements
	 *    if class E has a copy constructor
	 *        loop through the input port
	 *            copy element
	 *            write copy to a linked list
	 *        convert list to an array
	 *    if class E defines clone()
	 *        loop through the array
	 *            clone element
	 *            write clone to a linked list
	 *        convert list to an array
	 *else
	 *    while not EOF
	 *        read an object reference from the input port
	 *        write it to a list
	 *    convert the list to an array
	 *close input port
	 *</pre>
	 */
	@SuppressWarnings("unchecked")
	@Override
    public void run() {
		ArrayList<E> list = new ArrayList<E>(NativeBuffers.getBufferSize());
    	try {
    		if (isDuplicating()) {
				E element = input.read();
    			Class<?> clazz = element.getClass();
    			Constructor constructor = clazz.getDeclaredConstructor(clazz);    			
    			if (constructor != null) {
    	    		while (!Thread.currentThread().isInterrupted()) {
    	    			list.add((E) constructor.newInstance(element));
        				element = input.read();
    	    		}
    			} else {
    				Method clone = clazz.getDeclaredMethod("clone");
    				while (!Thread.currentThread().isInterrupted()) {
    					list.add((E) clone.invoke(element));    				
        				element = input.read();
    				}
    			}
    		} else { // Default: not duplicating:
    			while (!Thread.currentThread().isInterrupted()) {
    				E element = input.read();
    				list.add(element);
    			}
    		}
        } catch (InterruptedIOException i) {
        	// interruption request
		} catch (SecurityException e) {
            log.error("WriteArray#run()", e);
		} catch (NoSuchMethodException e) {
            log.error("WriteArray#run()", e);
		} catch (IllegalArgumentException e) {
            log.error("WriteArray#run()", e);
		} catch (InstantiationException e) {
            log.error("WriteArray#run()", e);
		} catch (IllegalAccessException e) {
            log.error("WriteArray#run()", e);
		} catch (InvocationTargetException e) {
            log.error("WriteArray#run()", e);
        } catch (EOFException e) {
        	// we're done reading
        } catch (IOException e) {
        	// pipe synch error
            log.error("WriteArray#run()", e);
		} finally {
			input.close();
			int size = list.size(); 
			if (size > 0) {
				if (array == null) {
					array = (E[]) Array.newInstance(
							          list.get(0).getClass(), size);
				}
				array = list.toArray(array);
			}
        }
    }

	/**
	 * Sets the array that provides the source data.
	 * 
	 * @param array The array reference to set.
	 */
	public void setArray(E[] array) {
    	if (array == null)
    		throw new IllegalArgumentException("array == null");
		this.array = array;
	}

	/**
	 * Sets whether to make element duplicates.
	 * 
	 * @param duplicating The {@code boolean} value to set.
	 */
	public void setDuplicating(boolean duplicating) {
		this.duplicating = duplicating;
	}

	/**
	 * Sets the input port.
	 * 
	 * @param input The input port.
	 */
    public void setInput(final InputPort<E> input) {
    	if (input == null)
    		throw new IllegalArgumentException("input == null");
 		this.input = input;
	}

}
